import adapter from 'webrtc-adapter'
Janus.sessions={};

var defaultExtension = {
	// Screensharing Chrome Extension ID
	extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj',
	init: function () {
		// var cache = {};
		// this.cache = cache;
		// Wait for events from the Chrome Extension
		window.addEventListener('message', function (event) {
			if(event.origin != window.location.origin)
				return;
		});
	}
};


export function Janus(gatewayCallbacks){

	Janus.init({debug: "none", callback:Janus.noop});

	var ws;
	var elementVideo = gatewayCallbacks.elementVideo;
	var sessionId;
	var that = this;
	var pluginHandle;
	var connected = false;
	var websockets = false;
	var wsHandlers;
	var count = 0;


	var servers = null, serversIndex = 0;
	var server = gatewayCallbacks.server;

	if(!Janus.isWebrtcSupported()) {
	    gatewayCallbacks.error({code:101, info:"WebRTC not supported by this browser"});
	    return {};
	}
	if( typeof gatewayCallbacks.onlogin != typeof Janus.noop){
		Janus.error("102:Parameter error");
		gatewayCallbacks.error({code:102, info:"Parameter error"});
		return {};
	}
	this.isConnected = function() { return connected; };

	this.reconnect = function(callbacks) {
		callbacks = callbacks || {};
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
		callbacks["reconnect"] = true;
		createSession(callbacks);
	};

	// this.getcount = function() { return count; };
	// this.plusCount = function() { return ++count;};
	// this.decreaseCount = function() { return --count; };
	// this.addElement = function(element) { elements.add(element); }
	// this.deleteSetmember = function(element) { elements.delete(element); }
	this.getsessionId = function() { return sessionId; };
	this.gethandle = function() { return pluginHandle; };
	this.getelementVideo = function() { return elementVideo;}
	this.setelementVideo = function(element) { elementVideo = element;}
	this.getwebsocket = function() {return ws};
	this.destroyWebsocket = function(callbacks) { destroyWebsocket(callbacks); };
	this.destroySession = function(callbacks) { destroySession(callbacks); };
	this.attachMediaStream = function(element, stream) { Janus.attachMediaStream(sessionId, element, stream); };
	// this.reattachMediaStream = function(to, from) { Janus.reattachMediaStream(sessionId, to, from); };
	this.deattachMediaStream = function(element) { Janus.deattachMediaStream(sessionId, element); };


	if(Janus.isArray(server)) {
		Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
		server = null;
		servers = gatewayCallbacks.server;
		Janus.debug(servers);
	} else {
		if(server.indexOf("ws") === 0) {
			websockets = true;
			Janus.log("Using WebSockets to contact Janus: " + server);
		} else {
			websockets = false;
			Janus.log("Using REST API to contact Janus: " + server);
			gatewayCallbacks.error({code:103, info:"Unsupport contact interface"});
			return {};
		}
	}

	var iceServers = gatewayCallbacks.iceServers;
	if(iceServers === undefined || iceServers === null)
		iceServers = [{urls: "stun:stun.l.google.com:19302"}];

	var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;

	var bundlePolicy = gatewayCallbacks.bundlePolicy;

	// Whether IPv6 candidates should be gathered
	var ipv6Support = gatewayCallbacks.ipv6;
	if(ipv6Support === undefined || ipv6Support === null)
		ipv6Support = false;

	var connection_timer = null;
	var connection_timeout = gatewayCallbacks.connection_timer || 30000;
	connection_timer = window.setInterval(function () {
		// window.clearInterval(connection_timer);
		destroySession();
		gatewayCallbacks.error({code: 401, info: "Time out!"});
		// if(stream)
		// 	window.clearInterval(polly);
		// if(stream.currentTime == lastTime) {
		// 	window.clearInterval(polly);
		// 	if(stream.onended) {
		// 		stream.onended();
		// 	}
		// }
		// lastTime = stream.currentTime;
	}, connection_timeout);

	pluginHandle = createHandle(gatewayCallbacks);
	createSession(gatewayCallbacks);

	// Private method to create a session
	function createSession(callbacks) {
		if(callbacks["reconnect"]) {
			// We're reconnecting, claim the session
			connected = false;

			// If we were using websockets, ignore the old connection
			if(ws) {
				// ws.onopen = null;
				// ws.onerror = null;
				// ws.onclose = null;
				destroyWebsocket();
			}
		}

		if(server === null && Janus.isArray(servers)) {
			// We still need to find a working server from the list we were given
			server = servers[serversIndex];
			if(server.indexOf("ws") === 0) {
				websockets = true;
				Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
			} else {
				websockets = false;
				Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
			}
		}
		if(websockets) {
			// ws = Janus.newWebSocket(server, 'janus-protocol');
			ws = Janus.newWebSocket(server);
			wsHandlers = {
				'error': function() {
					if(connection_timer){
						window.clearInterval(connection_timer);
						connection_timer = null;
					}
					destroySession();
					Janus.error("Error connecting to the Janus WebSockets server... " + server);
					callbacks.error({code: 201, info: "Failed connecting to the Janus WebSockets server"});
					// if (Janus.isArray(servers) && !callbacks["reconnect"]) {
					// 	serversIndex++;
					// 	if (serversIndex == servers.length) {
					// 		// We tried all the servers the user gave us and they all failed
					// 		if(connection_timer){
					// 			window.clearInterval(connection_timer);
					// 			connection_timer = null;
					// 		}
					// 		callbacks.error("Error connecting to any of the provided Singling servers: Is the server down?");
					// 		return;
					// 	}
					// 	// Let's try the next server
					// 	server = null;
					// 	setTimeout(function() {
					// 		createSession(callbacks);
					// 	}, 200);
					// 	return;
					// }

				},

				'open': function() {
					connected = true;
					Janus.sessions[sessionId] = that;
					 //���������������ע����Ϣ
					ws_send(sessionId, {
						type: "login",
					});

					// ws.send(JSON.stringify(request));
				},

				'message': function(msg){

					var data = JSON.parse(msg.data);

					switch(data.type) {
						case "login":
							ws_recv_login(data);
							break;
						case "offer":
							ws_recv_offer(data.offer);
							break;
						case "answer":
							// ws_recv_answer(data.answer);
							break;
						case "candidate":
							ws_recv_candidate(data.candidate);
							break;
						// case "leave":
						// 	ws_recv_leave();
						//  break;
						default:
							break;
					}
				},

				'close': function() {
					if (server === null || !connected) {
						return;
					}
					// FIXME What if this is called when the page is closed?
					Janus.warn("Lost connection to the server (is it down?)");
				}
			};

			for(var eventName in wsHandlers) {
				ws.addEventListener(eventName, wsHandlers[eventName]);
			}

			return;
		}
	}

	function ws_send(sessionId, msg){
		try{
			ws.send(JSON.stringify(msg));
		}catch(ex){
			Janus.warn("SYNTAX_ERR or INVALID_STATE_ERR, msg:" + msg);
		}
	}

	function ws_recv_candidate(candidate) {
		// console.log("recv candidate:" + event);
		pluginHandle.webrtcStuff.pc.addIceCandidate(new RTCIceCandidate(candidate));
	}

	function ws_recv_login(data) {
		if (data.success === false) {
			if(connection_timer){
				window.clearInterval(connection_timer);
				connection_timer = null;
			}
			destroyWebsocket({keepSessionId:false});
			gatewayCallbacks.error({code:202, info: "Login Singling failed: Is the server down?"});
		} else {
			gatewayCallbacks.onlogin(data);
		}
	};

	function ws_recv_offer(jsep) {
		pluginHandle.createAnswer(
			{
				jsep: jsep,
				// We want recvonly audio/video and, if negotiated, datachannels
				media: { audioSend: false, videoSend: false, data: true },
				success: function(jsep) {
					Janus.debug("Got SDP!");
					Janus.debug(jsep);
					pluginHandle.send({"type": "answer", "answer": jsep});
				},
				error: function(error) {
					destroySession();
					Janus.error("pluginHandle failed to create answer");
					gatewayCallbacks.error(error);
					// return ;
				}
			}
		);
	}

	function createHandle(callbacks) {
		//var sessionId;
		if(gatewayCallbacks.sessionId === null || gatewayCallbacks.sessionId === undefined) {
			sessionId= Janus.randomId();
			Janus.sessions[sessionId] = that;
			// var handle = that.gethandle();
		}
		callbacks = callbacks || {};
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
		callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop;
		callbacks.iceState = (typeof callbacks.iceState == "function") ? callbacks.iceState : iceStateChangeCb;
		callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop;
		callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop;
		callbacks.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : Janus.noop;
		callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop;
		callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop;
		callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop;
		callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop;
		callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop;
		callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop;
		callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop;
		var plugin =
		{
			server: server,
			sessionId: sessionId,
			webrtcStuff:{
				started : false,
				myStream : null,
				streamExternal : false,
				remoteStream : null,
				mySdp : null,
				mediaConstraints : null,
				pc : null,
				dataChannel : {},
				dtmfSender : null,
				trickle : true,
				iceDone : false,
				volume : {
					value : null,
					timer : null
				},
				bitrate : {
					value : null,
					bsnow : null,
					bsbefore : null,
					tsnow : null,
					tsbefore : null,
					timer : null
				}
			},
			getId : function() { return sessionId; },
			getVolume : function() { return getVolume(sessionId, true); },
			getRemoteVolume : function() { return getVolume(sessionId, true); },
			getLocalVolume : function() { return getVolume(sessionId, false); },
			isAudioMuted : function() { return isMuted(sessionId, false); },
			muteAudio : function() { return mute(sessionId, false, true); },
			unmuteAudio : function() { return mute(sessionId, false, false); },
			isVideoMuted : function() { return isMuted(sessionId, true); },
			muteVideo : function() { return mute(sessionId, true, true); },
			unmuteVideo : function() { return mute(sessionId, true, false); },
			getBitrate : function() { return getBitrate(sessionId); },
			send : function(callbacks) { ws_send(sessionId, callbacks); },
			data : function(callbacks) { sendData(sessionId, callbacks); },
			dtmf : function(callbacks) { sendDtmf(sessionId, callbacks); },
			consentDialog : callbacks.consentDialog,
			iceState : callbacks.iceState,
			mediaState : callbacks.mediaState,
			webrtcState : callbacks.webrtcState,
			slowLink : callbacks.slowLink,
			onmessage : callbacks.onmessage,
			createOffer : function(callbacks) { prepareWebrtc(sessionId, true, callbacks); },
			createAnswer : function(callbacks) { prepareWebrtc(sessionId, false, callbacks); },
			handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(sessionId, callbacks); },
			onlocalstream : callbacks.onlocalstream,
			onremotestream : callbacks.onremotestream,
			ondata : callbacks.ondata,
			ondataopen : callbacks.ondataopen,
			oncleanup : callbacks.oncleanup,
			ondetached : callbacks.ondetached,
			hangup : function() { cleanupWebrtc(); },
			detach : function(callbacks) { destroyHandle(sessionId, callbacks); }
		}

		return plugin;
	}

	// WebRTC stuff
	function streamsDone(sessionId, jsep, media, callbacks, stream) {
		// if(pluginHandle === null || pluginHandle === undefined ||
		// 		pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
		// 	Janus.warn("Invalid handle");
		// 	callbacks.error("Invalid handle");
		// 	return;
		// }
		var config = pluginHandle.webrtcStuff;
		Janus.debug("streamsDone:", stream);
		if(stream) {
			Janus.debug("  -- Audio tracks:", stream.getAudioTracks());
			Janus.debug("  -- Video tracks:", stream.getVideoTracks());
		}
		// We're now capturing the new stream: check if we're updating or if it's a new thing
		var addTracks = false;
		if(!config.myStream || !media.update || config.streamExternal) {
			config.myStream = stream;
			addTracks = true;
		} else {
			// We only need to update the existing stream
			if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) &&
					stream.getAudioTracks() && stream.getAudioTracks().length) {
				config.myStream.addTrack(stream.getAudioTracks()[0]);
				if((Janus.webRTCAdapter.webrtcDetectedBrowser === "firefox" && Janus.webRTCAdapter.webrtcDetectedVersion >= 59) ||
						(Janus.webRTCAdapter.webrtcDetectedBrowser === "safari" && window.RTCRtpSender.prototype.replaceTrack) ||
						(Janus.webRTCAdapter.webrtcDetectedBrowser === "chrome" && Janus.webRTCAdapter.webrtcDetectedVersion >= 72)) {
					// Use Transceivers
					Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
					var audioTransceiver = null;
					var transceivers = config.pc.getTransceivers();
					if(transceivers && transceivers.length > 0) {
						for(var i in transceivers) {
							var t = transceivers[i];
							if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
									(t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
								audioTransceiver = t;
								break;
							}
						}
					}
					if(audioTransceiver && audioTransceiver.sender) {
						audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);
					} else {
						config.pc.addTrack(stream.getAudioTracks()[0], stream);
					}
				} else {
					Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
					config.pc.addTrack(stream.getAudioTracks()[0], stream);
				}
			}
			if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) &&
					stream.getVideoTracks() && stream.getVideoTracks().length) {
				config.myStream.addTrack(stream.getVideoTracks()[0]);
				if((Janus.webRTCAdapter.webrtcDetectedBrowser === "firefox" && Janus.webRTCAdapter.webrtcDetectedVersion >= 59) ||
						(Janus.webRTCAdapter.webrtcDetectedBrowser === "safari" && window.RTCRtpSender.prototype.replaceTrack) ||
						(Janus.webRTCAdapter.webrtcDetectedBrowser === "chrome" && Janus.webRTCAdapter.webrtcDetectedVersion >= 72)) {
					// Use Transceivers
					Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
					var videoTransceiver = null;
					var transceivers = config.pc.getTransceivers();
					if(transceivers && transceivers.length > 0) {
						for(var i in transceivers) {
							var t = transceivers[i];
							if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
									(t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
								videoTransceiver = t;
								break;
							}
						}
					}
					if(videoTransceiver && videoTransceiver.sender) {
						videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
					} else {
						config.pc.addTrack(stream.getVideoTracks()[0], stream);
					}
				} else {
					Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
					config.pc.addTrack(stream.getVideoTracks()[0], stream);
				}
			}
		}
		// If we still need to create a PeerConnection, let's do that
		if(!config.pc) {
			var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy};
			if(Janus.webRTCAdapter.webrtcDetectedBrowser === "chrome") {
				// For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise
				pc_config["sdpSemantics"] = (Janus.webRTCAdapter.webrtcDetectedVersion < 72) ? "plan-b" : "unified-plan";
			}
			var pc_constraints = {
				"optional": [{"DtlsSrtpKeyAgreement": true}]
			};
			if(ipv6Support === true) {
				pc_constraints.optional.push({"googIPv6":true});
			}
			// Any custom constraint to add?
			if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') {
				Janus.debug("Adding custom PeerConnection constraints:", callbacks.rtcConstraints);
				for(var i in callbacks.rtcConstraints) {
					pc_constraints.optional.push(callbacks.rtcConstraints[i]);
				}
			}
			if(Janus.webRTCAdapter.webrtcDetectedBrowser === "edge") {
				// This is Edge, enable BUNDLE explicitly
				pc_config.bundlePolicy = "max-bundle";
			}
			Janus.log("Creating PeerConnection");
			Janus.debug(pc_constraints);
			config.pc = new RTCPeerConnection(pc_config, pc_constraints);
			Janus.log(config.pc.remoteDescription);
			Janus.debug(config.pc);
			if(config.pc.getStats) {	// FIXME
				config.volume = {};
				config.bitrate.value = "0 kbits/sec";
			}
			Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
			config.pc.oniceconnectionstatechange = function(e) {
				if(config.pc)
					pluginHandle.iceState(config.pc.iceConnectionState);
			};
			config.pc.onicecandidate = function(event) {
				if (event.candidate == null ||
						(Janus.webRTCAdapter.webrtcDetectedBrowser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) {
					Janus.log("End of candidates.");
					config.iceDone = true;
					if(config.trickle === true) {
						// Notify end of candidates
						sendTrickleCandidate(sessionId, {"completed": true});
						destroyWebsocket();
					} else {
						// No trickle, time to send the complete SDP (including all candidates)
						// sendSDP(sessionId, callbacks);
					}
				} else {
					// JSON.stringify doesn't work on some WebRTC objects anymore
					// See https://code.google.com/p/chromium/issues/detail?id=467366
					var candidate = {
						"candidate": event.candidate.candidate,
						"sdpMid": event.candidate.sdpMid,
						"sdpMLineIndex": event.candidate.sdpMLineIndex
					};
					if(config.trickle === true) {
						// Send candidate
						sendTrickleCandidate(sessionId, candidate);
					}
				}
			};
			config.pc.ontrack = function(event) {
				Janus.log("Handling Remote Track");
				Janus.debug(event);
				if(!event.streams)
					return;
				config.remoteStream = event.streams[0];
				pluginHandle.onremotestream(config.remoteStream);
				if(connection_timer){
					window.clearInterval(connection_timer);
					connection_timer = null;
				}
				if(event.track && !event.track.onended) {
					Janus.log("Adding onended callback to track:", event.track);
					event.track.onended = function(ev) {
						Janus.log("Remote track removed:", ev);
						if(config.remoteStream) {
							config.remoteStream.removeTrack(ev.target);
							pluginHandle.onremotestream(config.remoteStream);
						}
					}
				}
			};
		}

		if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) {
			Janus.log("Creating data channel");
			createDataChannel(sessionId, Janus.dataChanDefaultLabel, false);
			config.pc.ondatachannel = function(event) {
				Janus.log("Data channel created by Janus:", event);
				createDataChannel(sessionId, event.channel.label, event.channel);
			};
		}
		// If there's a new local stream, let's notify the application
		if(config.myStream)
			pluginHandle.onlocalstream(config.myStream);
		// Create offer/answer now
		if(jsep === null || jsep === undefined) {
			createOffer(sessionId, media, callbacks);
		} else {
			config.pc.setRemoteDescription(jsep)
				.then(function() {
					Janus.log("Remote description accepted!");
					config.remoteSdp = jsep.sdp;
					// Any trickle candidate we cached?
					if(config.candidates && config.candidates.length > 0) {
						for(var i in config.candidates) {
							var candidate = config.candidates[i];
							Janus.debug("Adding remote candidate:", candidate);
							if(!candidate || candidate.completed === true) {
								// end-of-candidates
								config.pc.addIceCandidate({candidate:''});
							} else {
								// New candidate
								config.pc.addIceCandidate(candidate);
							}
						}
						config.candidates = [];
					}
					// Create the answer now
					createAnswer(sessionId, media, callbacks);
				}, callbacks.error);
		}
	}

	function prepareWebrtc(sessionId, offer, callbacks) {
		callbacks = callbacks || {};
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
		var jsep = callbacks.jsep;
		if(offer && jsep) {
			Janus.error("Provided a JSEP to a createOffer");
			callbacks.error({code: 302, info: "Provided a JSEP to a createOffer"});
			destroySession();
			return ;
		} else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) {
			Janus.error("A valid JSEP is required for createAnswer");
			callbacks.error({code: 303, info: "A valid JSEP is required for createAnswer"});
			destroySession();
			return;
		}
		callbacks.media = callbacks.media || { audio: true, video: true };
		var media = callbacks.media;

		// if(pluginHandle === null || pluginHandle === undefined ||
		// 		pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
		// 	Janus.warn("Invalid handle");
		// 	callbacks.error("Invalid handle");
		// 	return;
		// }
		var config = pluginHandle.webrtcStuff;
		config.trickle = isTrickleEnabled(callbacks.trickle);
		// Are we updating a session?
		if(config.pc === undefined || config.pc === null) {
			// Nope, new PeerConnection
			media.update = false;
			media.keepAudio = false;
			media.keepVideo = false;
		} else if(config.pc !== undefined && config.pc !== null) {
			Janus.log("Updating existing media session");
			media.update = true;
			// Check if there's anything to add/remove/replace, or if we
			// can go directly to preparing the new SDP offer or answer
			if(callbacks.stream !== null && callbacks.stream !== undefined) {
				// External stream: is this the same as the one we were using before?
				if(callbacks.stream !== config.myStream) {
					Janus.log("Renegotiation involves a new external stream");
				}
			} else {
				// Check if there are changes on audio
				if(media.addAudio) {
					media.keepAudio = false;
					media.replaceAudio = false;
					media.removeAudio = false;
					media.audioSend = true;
					if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
						Janus.error("Can't add audio stream, there already is one");
						callbacks.error("Can't add audio stream, there already is one");
						return;
					}
				} else if(media.removeAudio) {
					media.keepAudio = false;
					media.replaceAudio = false;
					media.addAudio = false;
					media.audioSend = false;
				} else if(media.replaceAudio) {
					media.keepAudio = false;
					media.addAudio = false;
					media.removeAudio = false;
					media.audioSend = true;
				}
				if(config.myStream === null || config.myStream === undefined) {
					// No media stream: if we were asked to replace, it's actually an "add"
					if(media.replaceAudio) {
						media.keepAudio = false;
						media.replaceAudio = false;
						media.addAudio = true;
						media.audioSend = true;
					}
					if(isAudioSendEnabled(media)) {
						media.keepAudio = false;
						media.addAudio = true;
					}
				} else {
					if(config.myStream.getAudioTracks() === null
							|| config.myStream.getAudioTracks() === undefined
							|| config.myStream.getAudioTracks().length === 0) {
						// No audio track: if we were asked to replace, it's actually an "add"
						if(media.replaceAudio) {
							media.keepAudio = false;
							media.replaceAudio = false;
							media.addAudio = true;
							media.audioSend = true;
						}
						if(isAudioSendEnabled(media)) {
							media.keepVideo = false;
							media.addAudio = true;
						}
					} else {
						// We have an audio track: should we keep it as it is?
						if(isAudioSendEnabled(media) &&
								!media.removeAudio && !media.replaceAudio) {
							media.keepAudio = true;
						}
					}
				}
				// Check if there are changes on video
				if(media.addVideo) {
					media.keepVideo = false;
					media.replaceVideo = false;
					media.removeVideo = false;
					media.videoSend = true;
					if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
						Janus.error("Can't add video stream, there already is one");
						callbacks.error("Can't add video stream, there already is one");
						return;
					}
				} else if(media.removeVideo) {
					media.keepVideo = false;
					media.replaceVideo = false;
					media.addVideo = false;
					media.videoSend = false;
				} else if(media.replaceVideo) {
					media.keepVideo = false;
					media.addVideo = false;
					media.removeVideo = false;
					media.videoSend = true;
				}
				if(config.myStream === null || config.myStream === undefined) {
					// No media stream: if we were asked to replace, it's actually an "add"
					if(media.replaceVideo) {
						media.keepVideo = false;
						media.replaceVideo = false;
						media.addVideo = true;
						media.videoSend = true;
					}
					if(isVideoSendEnabled(media)) {
						media.keepVideo = false;
						media.addVideo = true;
					}
				} else {
					if(config.myStream.getVideoTracks() === null
							|| config.myStream.getVideoTracks() === undefined
							|| config.myStream.getVideoTracks().length === 0) {
						// No video track: if we were asked to replace, it's actually an "add"
						if(media.replaceVideo) {
							media.keepVideo = false;
							media.replaceVideo = false;
							media.addVideo = true;
							media.videoSend = true;
						}
						if(isVideoSendEnabled(media)) {
							media.keepVideo = false;
							media.addVideo = true;
						}
					} else {
						// We have a video track: should we keep it as it is?
						if(isVideoSendEnabled(media) &&
								!media.removeVideo && !media.replaceVideo) {
							media.keepVideo = true;
						}
					}
				}
				// Data channels can only be added
				if(media.addData)
					media.data = true;
			}
			// If we're updating and keeping all tracks, let's skip the getUserMedia part
			if((isAudioSendEnabled(media) && media.keepAudio) &&
					(isVideoSendEnabled(media) && media.keepVideo)) {
				pluginHandle.consentDialog(false);
				streamsDone(sessionId, jsep, media, callbacks, config.myStream);
				return;
			}
		}

		// No need to do a getUserMedia, create offer/answer right away
		streamsDone(sessionId, jsep, media, callbacks);
	}

	function iceStateChangeCb(state){
		Janus.warn("iceState: " + state);

		if(state === "disconnected"){
			destroySession();
			Janus.warn("P2P state: " + state);
			gatewayCallbacks.error({code:304, info: "P2P disconnected"});
			return ;
		} else if(state == "failed"){
			destroySession();
			Janus.error("P2P state: " + state);
			gatewayCallbacks.error({code:301, info: "P2P connection failed"});
			return ;
		} else if(state === "connected"){

		} else if(state === "closed"){

		}
	}

	function createAnswer(sessionId, media, callbacks) {
		callbacks = callbacks || {};
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
		callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
		// var pluginHandle = pluginHandles[sessionId];

		var config = pluginHandle.webrtcStuff;
		var simulcast = callbacks.simulcast === true ? true : false;
		if(!simulcast) {
			Janus.log("Creating answer (iceDone=" + config.iceDone + ")");
		} else {
			Janus.log("Creating answer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
		}
		var mediaConstraints = null;
		if((Janus.webRTCAdapter.webrtcDetectedBrowser === "firefox" && Janus.webRTCAdapter.webrtcDetectedVersion >= 59) ||
				(Janus.webRTCAdapter.webrtcDetectedBrowser === "safari" && window.RTCRtpSender.prototype.replaceTrack) ||
				(Janus.webRTCAdapter.webrtcDetectedBrowser === "chrome" && Janus.webRTCAdapter.webrtcDetectedVersion >= 72)) {
			// Firefox >= 59, Safari and Chrome >= 72 use Transceivers
			mediaConstraints = {};
			var audioTransceiver = null, videoTransceiver = null;
			var transceivers = config.pc.getTransceivers();
			if(transceivers && transceivers.length > 0) {
				for(var i in transceivers) {
					var t = transceivers[i];
					if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
							(t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
						if(!audioTransceiver)
							audioTransceiver = t;
						continue;
					}
					if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
							(t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
						if(!videoTransceiver)
							videoTransceiver = t;
						continue;
					}
				}
			}
			// Handle audio (and related changes, if any)
			var audioSend = isAudioSendEnabled(media);
			var audioRecv = isAudioRecvEnabled(media);
			if(!audioSend && !audioRecv) {
				// Audio disabled: have we removed it?
				if(media.removeAudio && audioTransceiver) {
					if (audioTransceiver.setDirection) {
						audioTransceiver.setDirection("inactive");
					} else {
						audioTransceiver.direction = "inactive";
					}
					Janus.log("Setting audio transceiver to inactive:", audioTransceiver);
				}
			} else {
				// Take care of audio m-line
				if(audioSend && audioRecv) {
					if(audioTransceiver) {
						if (audioTransceiver.setDirection) {
							audioTransceiver.setDirection("sendrecv");
						} else {
							audioTransceiver.direction = "sendrecv";
						}
						Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver);
					}
				} else if(audioSend && !audioRecv) {
					if(audioTransceiver) {
						if (audioTransceiver.setDirection) {
							audioTransceiver.setDirection("sendonly");
						} else {
							audioTransceiver.direction = "sendonly";
						}
						Janus.log("Setting audio transceiver to sendonly:", audioTransceiver);
					}
				} else if(!audioSend && audioRecv) {
					if(audioTransceiver) {
						if (audioTransceiver.setDirection) {
							audioTransceiver.setDirection("recvonly");
						} else {
							audioTransceiver.direction = "recvonly";
						}
						Janus.log("Setting audio transceiver to recvonly:", audioTransceiver);
					} else {
						// In theory, this is the only case where we might not have a transceiver yet
						audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" });
						Janus.log("Adding recvonly audio transceiver:", audioTransceiver);
					}
				}
			}
			// Handle video (and related changes, if any)
			var videoSend = isVideoSendEnabled(media);
			var videoRecv = isVideoRecvEnabled(media);
			if(!videoSend && !videoRecv) {
				// Video disabled: have we removed it?
				if(media.removeVideo && videoTransceiver) {
					if (videoTransceiver.setDirection) {
						videoTransceiver.setDirection("inactive");
					} else {
						videoTransceiver.direction = "inactive";
					}
					Janus.log("Setting video transceiver to inactive:", videoTransceiver);
				}
			} else {
				// Take care of video m-line
				if(videoSend && videoRecv) {
					if(videoTransceiver) {
						if (videoTransceiver.setDirection) {
							videoTransceiver.setDirection("sendrecv");
						} else {
							videoTransceiver.direction = "sendrecv";
						}
						Janus.log("Setting video transceiver to sendrecv:", videoTransceiver);
					}
				} else if(videoSend && !videoRecv) {
					if(videoTransceiver) {
						if (videoTransceiver.setDirection) {
							videoTransceiver.setDirection("sendonly");
						} else {
							videoTransceiver.direction = "sendonly";
						}
						Janus.log("Setting video transceiver to sendonly:", videoTransceiver);
					}
				} else if(!videoSend && videoRecv) {
					if(videoTransceiver) {
						if (videoTransceiver.setDirection) {
							videoTransceiver.setDirection("recvonly");
						} else {
							videoTransceiver.direction = "recvonly";
						}
						Janus.log("Setting video transceiver to recvonly:", videoTransceiver);
					} else {
						// In theory, this is the only case where we might not have a transceiver yet
						videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" });
						Janus.log("Adding recvonly video transceiver:", videoTransceiver);
					}
				}
			}
		} else {
			if(Janus.webRTCAdapter.webrtcDetectedBrowser == "firefox" || Janus.webRTCAdapter.webrtcDetectedBrowser == "edge") {
				mediaConstraints = {
					offerToReceiveAudio: isAudioRecvEnabled(media),
					offerToReceiveVideo: isVideoRecvEnabled(media)
				};
			} else {
				mediaConstraints = {
					mandatory: {
						OfferToReceiveAudio: isAudioRecvEnabled(media),
						OfferToReceiveVideo: isVideoRecvEnabled(media)
					}
				};
			}
		}
		Janus.debug(mediaConstraints);
		// Check if this is Firefox and we've been asked to do simulcasting
		var sendVideo = isVideoSendEnabled(media);
		if(sendVideo && simulcast && Janus.webRTCAdapter.webrtcDetectedBrowser === "firefox") {
			// FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
			Janus.log("Enabling Simulcasting for Firefox (RID)");
			var sender = config.pc.getSenders()[1];
			Janus.log(sender);
			var parameters = sender.getParameters();
			Janus.log(parameters);
			sender.setParameters({encodings: [
				{ rid: "high", active: true, priority: "high", maxBitrate: 1000000 },
				{ rid: "medium", active: true, priority: "medium", maxBitrate: 300000 },
				{ rid: "low", active: true, priority: "low", maxBitrate: 100000 }
			]});
		}
		config.pc.createAnswer(mediaConstraints)
			.then(function(answer) {
				Janus.debug(answer);
				// JSON.stringify doesn't work on some WebRTC objects anymore
				// See https://code.google.com/p/chromium/issues/detail?id=467366
				var jsep = {
					"type": answer.type,
					"sdp": answer.sdp
				};
				callbacks.customizeSdp(jsep);
				answer.sdp = jsep.sdp;
				Janus.log("Setting local description");
				if(sendVideo && simulcast) {
					// This SDP munging only works with Chrome
					if(Janus.webRTCAdapter.webrtcDetectedBrowser === "chrome") {
						// FIXME Apparently trying to simulcast when answering breaks video in Chrome...
						//~ Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
						//~ answer.sdp = mungeSdpForSimulcasting(answer.sdp);
						Janus.warn("simulcast=true, but this is an answer, and video breaks in Chrome if we enable it");
					} else if(Janus.webRTCAdapter.webrtcDetectedBrowser !== "firefox") {
						Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
					}
				}
				config.mySdp = answer.sdp;
				config.pc.setLocalDescription(answer)
					.catch(callbacks.error);
				config.mediaConstraints = mediaConstraints;
				if(!config.iceDone && !config.trickle) {
					// Don't do anything until we have all candidates
					Janus.log("Waiting for all candidates...");
					return;
				}
				callbacks.success(answer);
			}, callbacks.error);
	}

	function cleanupWebrtc(hangupRequest) {
		Janus.log("Cleaning WebRTC stuff");

		if(pluginHandle === null || pluginHandle === undefined) {
			// Nothing to clean
			Janus.log("No WEBRTC stuff to clean");
			return;
		}
		var config = pluginHandle.webrtcStuff;
		if(config !== null && config !== undefined) {
			if(hangupRequest) {
				// Send a hangup request (we don't really care about the response)
				// Todo: if needed
			}
			// Cleanup stack
			config.remoteStream = null;
			if(config.volume) {
				if(config.volume["local"] && config.volume["local"].timer)
					clearInterval(config.volume["local"].timer);
				if(config.volume["remote"] && config.volume["remote"].timer)
					clearInterval(config.volume["remote"].timer);
			}
			config.volume = {};
			if(config.bitrate.timer)
				clearInterval(config.bitrate.timer);
			config.bitrate.timer = null;
			config.bitrate.bsnow = null;
			config.bitrate.bsbefore = null;
			config.bitrate.tsnow = null;
			config.bitrate.tsbefore = null;
			config.bitrate.value = null;
			try {
				// Try a MediaStreamTrack.stop() for each track
				if(!config.streamExternal && config.myStream !== null && config.myStream !== undefined) {
					Janus.log("Stopping local stream tracks");
					var tracks = config.myStream.getTracks();
					for(var i in tracks) {
						var mst = tracks[i];
						Janus.log(mst);
						if(mst !== null && mst !== undefined)
							mst.stop();
					}
				}
			} catch(e) {
				// Do nothing if this fails
			}
			config.streamExternal = false;
			config.myStream = null;
			// Close PeerConnection
			try {
				config.pc.close();
			} catch(e) {
				// Do nothing
			}
			config.pc = null;
			config.candidates = null;
			config.mySdp = null;
			config.remoteSdp = null;
			config.iceDone = false;
			config.dataChannel = {};
			config.dtmfSender = null;
		}
		// connected = false;
		//user define, if needed
		pluginHandle.oncleanup();
	}

	// Private method to create a data channel
	function createDataChannel(sessionId, dclabel, incoming, pendingText) {

		if(pluginHandle === null || pluginHandle === undefined ||
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
			Janus.warn("Invalid handle");
			return;
		}
		var config = pluginHandle.webrtcStuff;
		var onDataChannelMessage = function(event) {
			Janus.log('Received message on data channel:', event);
			var label = event.target.label;
			pluginHandle.ondata(event.data, label);
		}
		var onDataChannelStateChange = function(event) {
			Janus.log('Received state change on data channel:', event);
			var label = event.target.label;
			var dcState = config.dataChannel[label] ? config.dataChannel[label].readyState : "null";
			Janus.log('State change on <' + label + '> data channel: ' + dcState);
			if(dcState === 'open') {
				// Any pending messages to send?
				if(config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) {
					Janus.log("Sending pending messages on <' + label + '>:", config.dataChannel[label].pending.length);
					for(var i in config.dataChannel[label].pending) {
						var text = config.dataChannel[label].pending[i];
						Janus.log("Sending string on data channel <" + label + ">: " + text);
						config.dataChannel[label].send(text);
					}
					config.dataChannel[label].pending = [];
				}
				// Notify the open data channel
				pluginHandle.ondataopen(label);
			}
		}
		var onDataChannelError = function(error) {
			Janus.error('Got error on data channel:', error);
			// TODO
		}
		if(!incoming) {
			// FIXME Add options (ordered, maxRetransmits, etc.)
			config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, {ordered:false});
		} else {
			// The channel was created by Janus
			config.dataChannel[dclabel] = incoming;
		}
		config.dataChannel[dclabel].onmessage = onDataChannelMessage;
		config.dataChannel[dclabel].onopen = onDataChannelStateChange;
		config.dataChannel[dclabel].onclose = onDataChannelStateChange;
		config.dataChannel[dclabel].onerror = onDataChannelError;
		config.dataChannel[dclabel].pending = [];
		if(pendingText)
			config.dataChannel[dclabel].pending.push(pendingText);
	}

	// Private method to send a trickle candidate
	function sendTrickleCandidate(sessionId, candidate) {
		if(!connected) {
			Janus.warn("Is the server down? (connected=false)");
			return;
		}

		if(pluginHandle === null || pluginHandle === undefined ||
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
			Janus.warn("Invalid handle");
			return;
		}
		var request = { "type": "candidate", "candidate": candidate };

		Janus.vdebug("Sending trickle candidate (handle=" + sessionId + "):");
		Janus.vdebug(request);
		pluginHandle.send(request);
	}

	// Private method to destroy a session
	function destroySession(callbacks) {
		callbacks = callbacks || {};
		// FIXME This method triggers a success even when we fail
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;

		Janus.log("Destroying session" + sessionId);

		if(connection_timer) {
			window.clearInterval(connection_timer);
			connection_timer = null;
		}

		if(!connected) {
			Janus.warn("Is the server down? (connected=false)");
			callbacks.success();
			return;
		}
		if(sessionId === undefined || sessionId === null) {
			Janus.warn("No session to destroy");
			callbacks.success();
			return;
		}

		var element = Janus.sessions[sessionId].getelementVideo();
		if( !(element === undefined || element === null)){
			Janus.deattachMediaStream(sessionId, element);
		}
		// var ws = Janus.sessions[sessionId].getwebsocket();
		if(ws != null) {
			destroyWebsocket();
		}
		// var handle = Janus.sessions[sessionId].gethandle();
		if(pluginHandle.webrtcStuff != null || pluginHandle.webrtcStuff != undefined) {
			cleanupWebrtc();
		}
		delete Janus.sessions[sessionId];
		connected = false;
		return;
	}

	function webrtcError(error) {
		Janus.error("WebRTC error:", error);
	}

	// Helper methods to parse a media object
	function isAudioSendEnabled(media) {
		Janus.debug("isAudioSendEnabled:", media);
		if(media === undefined || media === null)
			return true;	// Default
		if(media.audio === false)
			return false;	// Generic audio has precedence
		if(media.audioSend === undefined || media.audioSend === null)
			return true;	// Default
		return (media.audioSend === true);
	}

	function isAudioSendRequired(media) {
		Janus.debug("isAudioSendRequired:", media);
		if(media === undefined || media === null)
			return false;	// Default
		if(media.audio === false || media.audioSend === false)
			return false;	// If we're not asking to capture audio, it's not required
		if(media.failIfNoAudio === undefined || media.failIfNoAudio === null)
			return false;	// Default
		return (media.failIfNoAudio === true);
	}

	function isAudioRecvEnabled(media) {
		Janus.debug("isAudioRecvEnabled:", media);
		if(media === undefined || media === null)
			return true;	// Default
		if(media.audio === false)
			return false;	// Generic audio has precedence
		if(media.audioRecv === undefined || media.audioRecv === null)
			return true;	// Default
		return (media.audioRecv === true);
	}

	function isVideoSendEnabled(media) {
		Janus.debug("isVideoSendEnabled:", media);
		if(media === undefined || media === null)
			return true;	// Default
		if(media.video === false)
			return false;	// Generic video has precedence
		if(media.videoSend === undefined || media.videoSend === null)
			return true;	// Default
		return (media.videoSend === true);
	}

	function isVideoSendRequired(media) {
		Janus.debug("isVideoSendRequired:", media);
		if(media === undefined || media === null)
			return false;	// Default
		if(media.video === false || media.videoSend === false)
			return false;	// If we're not asking to capture video, it's not required
		if(media.failIfNoVideo === undefined || media.failIfNoVideo === null)
			return false;	// Default
		return (media.failIfNoVideo === true);
	}

	function isVideoRecvEnabled(media) {
		Janus.debug("isVideoRecvEnabled:", media);
		if(media === undefined || media === null)
			return true;	// Default
		if(media.video === false)
			return false;	// Generic video has precedence
		if(media.videoRecv === undefined || media.videoRecv === null)
			return true;	// Default
		return (media.videoRecv === true);
	}

	function isScreenSendEnabled(media) {
		Janus.debug("isScreenSendEnabled:", media);
		if (media === undefined || media === null)
			return false;
		if (typeof media.video !== 'object' || typeof media.video.mandatory !== 'object')
			return false;
		var constraints = media.video.mandatory;
		if (constraints.chromeMediaSource)
			return constraints.chromeMediaSource === 'desktop' || constraints.chromeMediaSource === 'screen';
		else if (constraints.mozMediaSource)
			return constraints.mozMediaSource === 'window' || constraints.mozMediaSource === 'screen';
		else if (constraints.mediaSource)
			return constraints.mediaSource === 'window' || constraints.mediaSource === 'screen';
		return false;
	}

	function isDataEnabled(media) {
		Janus.debug("isDataEnabled:", media);
		if(Janus.webRTCAdapter.webrtcDetectedBrowser == "edge") {
			Janus.warn("Edge doesn't support data channels yet");
			return false;
		}
		if(media === undefined || media === null)
			return false;	// Default
		return (media.data === true);
	}

	function isTrickleEnabled(trickle) {
		Janus.debug("isTrickleEnabled:", trickle);
		if(trickle === undefined || trickle === null)
			return true;	// Default is true
		return (trickle === true);
	}

	function destroyWebsocket(callbacks) {
		callbacks = callbacks || {};
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
		callbacks.keepSessionId = (callbacks.keepSessionId === undefined || callbacks.keepSessionId === true) ? true: false;

		if(callbacks.keepSessionId === false){
			delete Janus.sessions[sessionId];
		}
		if (ws === null){
			Janus.log("Is the ws down? (ws = null)");
			callbacks.success();
		}
		if (websockets) {
			for(var eventName in wsHandlers) {
				// ws.removeEventListener(eventName, wsHandlers[eventName]);
			}
			ws.close();
			ws = null;
		}

	}

};

// Initialization
Janus.init = function(options) {
	options = options || {};
	options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
	if(Janus.initDone === true) {
		// Already initialized
		options.callback();
	} else {
		if(typeof console == "undefined" || typeof console.log == "undefined")
			console = { log: function() {} };
		// Console logging (all debugging disabled by default)
		Janus.trace = Janus.noop;
		Janus.debug = Janus.noop;
		Janus.vdebug = Janus.noop;
		Janus.log = Janus.noop;
		Janus.warn = Janus.noop;
		Janus.error = Janus.noop;
		if(options.debug === true || options.debug === "all") {
			// Enable all debugging levels
			Janus.trace = console.trace.bind(console);
			Janus.debug = console.debug.bind(console);
			Janus.vdebug = console.debug.bind(console);
			Janus.log = console.log.bind(console);
			Janus.warn = console.warn.bind(console);
			Janus.error = console.error.bind(console);
		} else if(Array.isArray(options.debug)) {
			for(var i in options.debug) {
				var d = options.debug[i];
				switch(d) {
					case "trace":
						Janus.trace = console.trace.bind(console);
						break;
					case "debug":
						Janus.debug = console.debug.bind(console);
						break;
					case "vdebug":
						Janus.vdebug = console.debug.bind(console);
						break;
					case "log":
						Janus.log = console.log.bind(console);
						break;
					case "warn":
						Janus.warn = console.warn.bind(console);
						break;
					case "error":
						Janus.error = console.error.bind(console);
						break;
					default:
						console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
						break;
				}
			}
		}
		Janus.log("Initializing library");

		var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
		Janus.isArray = usedDependencies.isArray;
		Janus.webRTCAdapter = adapter;
		Janus.httpAPICall = usedDependencies.httpAPICall;
		Janus.newWebSocket = usedDependencies.newWebSocket;
		Janus.extension = usedDependencies.extension;
		Janus.extension.init();

		// Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
		Janus.attachMediaStream = function(sessionId, element, stream) {
			var janus_obj = Janus.sessions[sessionId];
			janus_obj.setelementVideo(element);
			if(Janus.webRTCAdapter.webrtcDetectedBrowser === 'chrome') {
				var chromever = Janus.webRTCAdapter.webrtcDetectedVersion;
				if(chromever >= 52) {
					element.srcObject = stream;
				} else if(typeof element.src !== 'undefined') {
					element.src = URL.createObjectURL(stream);
				} else {
					Janus.error("Error attaching stream to element");
					return ;
				}
			} else {
				element.srcObject = stream;
			}
			// Janus.sessions[sessionId].addElement(element);
			// Janus.sessions[sessionId].plusCount();
		};
		// Janus.reattachMediaStream = function(sessionId, to, from) {
		// 	if(Janus.webRTCAdapter.webrtcDetectedBrowser === 'chrome') {
		// 		var chromever = Janus.webRTCAdapter.webrtcDetectedVersion;
		// 		if(chromever >= 52) {
		// 			to.srcObject = from.srcObject;
		// 		} else if(typeof to.src !== 'undefined') {
		// 			to.src = from.src;
		// 		} else {
		// 			Janus.error("Error reattaching stream to element");
		// 			return ;
		// 		}
		// 	} else {
		// 		to.srcObject = from.srcObject;
		// 	}
		// 	Janus.sessions[sessionId].addElement(to);
		// 	Janus.sessions[sessionId].plusCount();
		// };

		Janus.deattachMediaStream = function(sessionId, element) {
			if(element === null || element === undefined) {
				Janus.warn("deattach element is null");
				return ;
			}
			if(Janus.webRTCAdapter.webrtcDetectedBrowser === 'chrome') {
				var chromever = Janus.webRTCAdapter.webrtcDetectedVersion;
				if(chromever >= 52) {
					element.srcObject = null;
				} else if(typeof to.src !== 'undefined') {
					element.src = null;
				} else {
					Janus.error("Error deattaching stream to element");
					return ;
				}
			} else {
				element.srcObject = null;
			}

			// Janus.sessions[sessionId].deleteSetmember(element);
			// var count = Janus.sessions[sessionId].decreaseCount();
			// if(count === 0){
			// 	// do not need deattach anymore
			// 	Janus.sessions[sessionId].destroySession({deattach: false});
			// }

			// return count;
		};
		// Detect tab close: make sure we don't loose existing onbeforeunload handlers
		// (note: for iOS we need to subscribe to a different event, 'pagehide', see
		// https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe)
		var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
		var eventName = iOS ? 'pagehide' : 'beforeunload';
		var oldOBF = window["on" + eventName];
		window.addEventListener(eventName, function(event) {
			Janus.log("Closing window");
			for(var s in Janus.sessions) {
				if(Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
						Janus.sessions[s].destroyOnUnload) {
					Janus.log("Destroying session " + s);
					Janus.sessions[s].destroy({asyncRequest: false, notifyDestroyed: false});
				}
			}
			if(oldOBF && typeof oldOBF == "function")
				oldOBF();
		});
		// If this is a Safari Technology Preview, check if VP8 is supported
		Janus.safariVp8 = false;
		if(Janus.webRTCAdapter.webrtcDetectedBrowser === 'safari' &&
				Janus.webRTCAdapter.webrtcDetectedVersion >= 605) {
			// Let's see if RTCRtpSender.getCapabilities() is there
			if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") &&
					RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) {
				for(var i in RTCRtpSender.getCapabilities("video").codecs) {
					var codec = RTCRtpSender.getCapabilities("video").codecs[i];
					if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") {
						Janus.safariVp8 = true;
						break;
					}
				}
				if(Janus.safariVp8) {
					Janus.log("This version of Safari supports VP8");
				} else {
					Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
						"try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
				}
			} else {
				// We do it in a very ugly way, as there's no alternative...
				// We create a PeerConnection to see if VP8 is in an offer
				var testpc = new RTCPeerConnection({}, {});
				testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) {
					Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1;
					if(Janus.safariVp8) {
						Janus.log("This version of Safari supports VP8");
					} else {
						Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
							"try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
					}
					testpc.close();
					testpc = null;
				});
			}
		}
		Janus.initDone = true;
		options.callback();
	}
};


Janus.useDefaultDependencies = function (deps) {
	// var f = (deps && deps.fetch) || fetch;
	// var p = (deps && deps.Promise) || Promise;
	var socketCls = (deps && deps.WebSocket) || WebSocket;

	return {
		newWebSocket: function(server, proto) { return new socketCls(server, proto); },
		extension: (deps && deps.extension) || defaultExtension,
		isArray: function(arr) { return Array.isArray(arr); },
		webRTCAdapter: (deps && deps.adapter) || adapter
	}
};


// Helper method to check whether WebRTC is supported by this browser
Janus.isWebrtcSupported = function() {
	return window.RTCPeerConnection !== undefined && window.RTCPeerConnection !== null;
};

// Helper method to create random identifiers (e.g., transaction)
Janus.randomId = function() {
	var len = 12;
	var charSet = '0123456789';
	var randomString = '';
	for (var i = 0; i < len; i++) {
		var randomPoz = Math.floor(Math.random() * charSet.length);
		randomString += charSet.substring(randomPoz,randomPoz+1);
	}
	return parseInt(randomString);
};

Janus.noop = function (){};
